#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include <time.h>
#include <math.h>
#include <sys/types.h>
#include <mpi.h>
#include <sys/time.h>

#include "../libParallel/MPIMaster.h"
#include "../libParallel/MPIBasics.h"
#include "../libParallel/MPISlave.h"
#include "../libGRASP/MGrasp.h"
#include "Funcao.h"
#include "MersenneTwister.h"
#include "Util.h"
#include "Dts.h"
#include "lbfgs.h"

#include "Rosenbrock2.h"
#include "Zakharov.h"
#include "SumSquares.h"
#include "Branin.h"
#include "Easom.h"
#include "GoldsteinPrice.h"
#include "Shekel.h"
#include "Hartmann.h"
#include "Shubert.h"
#include "Beale.h"
#include "Booth.h"
#include "Bohachevsky.h"
#include "Hump.h"
#include "Matyas.h"
#include "Schwefel.h"

#include "Colville.h"
#include "Perm.h"
#include "Perm0.h"
#include "PowerSum.h"
#include "Griewank.h"
#include "Rastrigin.h"
#include "Trid.h"
#include "Powell.h"
#include "DixonPrice.h"
#include "Ackley.h"
#include "Levy.h"
#include "Sphere.h"

#include "ap.h"

#define PURO 	0
#define HIBRIDO 1
#define BFGS 	2

int64_t getMilisegundos() {
    struct timeval tempo;
    gettimeofday(&tempo, NULL);
    return (int64_t) tempo.tv_sec * 1000 + tempo.tv_usec / 1000;
}

MGrasp *initMGrasp(int iFuncNum, int n, Funcao **func) {
    int m;
    double *u, *l;
    double hs, he, plo;
    l = new double[n];
    u = new double[n];
    MGrasp *mgrasp;

    plo = 0.7;
    switch (iFuncNum) {
        case Funcao::ROSENBROCK:
            hs = 1.0;
            he = 0.1;
            if (n == 20) {
                hs = 0.1;
                he = 0.05;
            }

            for (int i = 0; i < n; i++) {
                l[i] = -5.0;
                u[i] = 10.0;
            }
            *func = new Rosenbrock2(n);
            break;
        case Funcao::ZAKHAROV:
            hs = 1.0;
            //hs = 2.0;
            if (n <= 5)
                he = 0.5;
            else if (n >= 20) {
                hs = 5.0;
                //hs = 5.0;
                he = 0.05;
            } else {
                he = 0.005;
            }

            for (int i = 0; i < n; i++) {
                l[i] = -5.0;
                u[i] = 10.0;
            }
            *func = new Zakharov(n);
            break;
        case Funcao::SUMSQUARES:
            hs = 1.0;
            he = 0.5;
            for (int i = 0; i < n; i++) {
                l[i] = -5.0;
                u[i] = 10.0;
            }
            *func = new SumSquares(n);
            break;
        case Funcao::BRANIN:
            hs = 1.0;
            he = 0.001;
            //hs = 0.1;
            //he = 0.05;
            l[0] = -5;
            l[1] = 0;
            u[0] = 10;
            u[1] = 15;
            *func = new Branin(n);
            break;

        case Funcao::GOLDSTEINPRICE:
            hs = 1.0;
            he = 1.0;
            //he = 0.05;
            for (int i = 0; i < n; i++) {
                l[i] = -2.0;
                u[i] = 2.0;
            }
            *func = new GoldsteinPrice(n);
            break;

        case Funcao::EASOM:
            hs = 1.0;
            he = 0.1;

            for (int i = 0; i < n; i++) {
                l[i] = -100.0;
                u[i] = 100.0;
            }
            *func = new Easom(n);
            break;

        case Funcao::SHEKEL:
            m = n;
            n = 4;
            delete []l;
            delete []u;

            l = new double[n];
            u = new double[n];

            hs = 1.0;
            he = 0.5;
            //hs = 0.1;
            //he = 0.05;
            for (int i = 0; i < n; i++) {
                l[i] = 0.0;
                u[i] = 10.0;
            }
            *func = new Shekel(n, m);
            break;

        case Funcao::HARTMANN:
            hs = 0.5;
            //he = 0.001;

            if (n == 3)
                he = 0.001;
            else
                he = 0.0001;
            //he = 0.005;

            for (int i = 0; i < n; i++) {
                l[i] = 0.0;
                u[i] = 1.0;
            }
            *func = new Hartmann(n, 4);
            break;

        case Funcao::SHUBERT:
            hs = 1.0;
            he = 0.01;
            //hs = 0.1;
            //he = 0.05;
            for (int i = 0; i < n; i++) {
                l[i] = -10.0;
                u[i] = 10.0;
            }
            *func = new Shubert(n);
            break;

        case Funcao::BEALE:
            hs = 1.0;
            he = 0.5;
            for (int i = 0; i < n; i++) {
                l[i] = -4.5;
                u[i] = 4.5;
            }
            *func = new Beale(n);
            break;

        case Funcao::BOOTH:
            hs = 1.0;
            he = 0.5;
            for (int i = 0; i < n; i++) {
                l[i] = -10.0;
                u[i] = 10.0;
            }
            *func = new Booth(n);
            break;

        case Funcao::BOHACHEVSKY:
            hs = 1.0;
            he = 0.5;
            for (int i = 0; i < n; i++) {
                l[i] = -50.0;
                u[i] = 100.0;
            }
            *func = new Bohachevsky(n);
            break;

        case Funcao::HUMP:
            hs = 1.0;
            he = 0.01;
            for (int i = 0; i < n; i++) {
                l[i] = -5.0;
                u[i] = 5.0;
            }
            *func = new Hump(n);
            break;

        case Funcao::MATYAS:
            hs = 1.0;
            he = 0.1;
            for (int i = 0; i < n; i++) {
                l[i] = -5.0;
                u[i] = 10.0;
            }
            *func = new Matyas(n);
            break;

        case Funcao::SCHWEFEL:
            if (n == 2) {
                hs = 5.0;
                he = 0.25;
            } else if (n == 6) {
                hs = 50.0;
                he = 0.25;
            }

            for (int i = 0; i < n; i++) {
                l[i] = -500.0;
                u[i] = 500.0;
            }
            *func = new Schwefel(n);
            break;

        case Funcao::COLVILLE:
            hs = 1.0;
            he = 0.1;
            for (int i = 0; i < n; i++) {
                l[i] = -10.0;
                u[i] = 10.0;
            }
            *func = new Colville(n);
            break;

        case Funcao::PERM:
            //hs = 1.0;
            //he = 0.01;
            hs = 0.1;
            he = 0.0125;
            for (int i = 0; i < n; i++) {
                l[i] = (double) -n;
                u[i] = (double) n;
            }
            *func = new Perm(n);
            break;

        case Funcao::PERM0:
            hs = 1.0;
            he = 0.01;
            //hs = 0.1;
            //he = 0.0125;
            for (int i = 0; i < n; i++) {
                l[i] = (double) -n;
                u[i] = (double) n;
            }
            *func = new Perm_0(n);
            break;

        case Funcao::POWERSUM:
            hs = 1.0;
            he = 0.1;
            for (int i = 0; i < n; i++) {
                l[i] = (double) 0.0;
                u[i] = (double) n;
            }
            *func = new PowerSum(n);
            break;


        case Funcao::GRIEWANK:
            hs = 10.0;
            he = 0.25;
            for (int i = 0; i < n; i++) {
                l[i] = -300.0;
                u[i] = 600.0;
            }
            *func = new Griewank(n);
            break;

        case Funcao::RASTRIGIN:
            //hs = 1.0;
            //he = 0.1;
            hs = 0.5;
            he = 0.1;
            for (int i = 0; i < n; i++) {
                l[i] = -2.56;
                u[i] = 5.12;
            }
            *func = new Rastrigin(n);
            break;


        case Funcao::TRID:
            if (n == 10) {
                hs = 5.0;
                he = 0.1;
            } else {
                hs = 1.0;
                he = 0.1;
            }

            for (int i = 0; i < n; i++) {
                l[i] = (double) -n*n;
                u[i] = (double) n*n;
            }

            *func = new Trid(n);
            break;

        case Funcao::POWELL:
            hs = 1.0;
            he = 0.1;
            for (int i = 0; i < n; i++) {
                l[i] = -4.0;
                u[i] = 5.0;
            }
            *func = new Powell(n);
            break;

        case Funcao::DIXONPRICE:
            hs = 5.0;
            he = 0.25;
            for (int i = 0; i < n; i++) {
                l[i] = -10.0;
                u[i] = 10.0;
            }
            *func = new DixonPrice(n);
            break;

        case Funcao::ACKLEY:
            hs = 5.0;
            he = 0.05;
            for (int i = 0; i < n; i++) {
                l[i] = -15.0;
                u[i] = 30.0;
            }
            *func = new Ackley(n);
            break;


        case Funcao::LEVY:
            hs = 1.0;
            he = 0.1;
            for (int i = 0; i < n; i++) {
                l[i] = -10.0;
                u[i] = 10.0;
            }
            *func = new Levy(n);
            break;

        case Funcao::SPHERE:
            hs = 2.0;
            he = 0.05;
            for (int i = 0; i < n; i++) {
                l[i] = -2.56;
                u[i] = 5.12;
            }
            *func = new Sphere(n);
            break;


    }

    mgrasp = new MGrasp(n, l, u, *func, hs, he, plo);
    return mgrasp;
}

void usage() {
    printf("\nForma de uso: ");
    printf(" CGrasp FN N MAX_EVALS RUNS TYPE M\n");
    printf(" FN: nome da funcao (ex: ROSENBROCK, ZAKHAROV, etc)\n");
    printf(" N: numero de dimensoes\n");
    printf(" MAX_EVALS: numero maximo de avaliacoes da funcao. Caso 0 rodar até encerrar o algoritmo\n");
    printf(" RUNS: numero de execucoes do CGrasp p\n");
    printf(" TYPE: tipo (PURO, HIBRIDO)\n");
    printf(" M: numero de correcoes da matriz do L-BFGS (m < n)\n\n");
}

int getFuncNumb(char *funcName) {

    if (!strcmp(funcName, "ROSENBROCK")) return Funcao::ROSENBROCK;
    if (!strcmp(funcName, "ZAKHAROV")) return Funcao::ZAKHAROV;
    if (!strcmp(funcName, "SUMSQUARES")) return Funcao::SUMSQUARES;
    if (!strcmp(funcName, "BRANIN")) return Funcao::BRANIN;
    if (!strcmp(funcName, "EASOM")) return Funcao::EASOM;
    if (!strcmp(funcName, "GOLDSTEINPRICE"))return Funcao::GOLDSTEINPRICE;
    if (!strcmp(funcName, "SHEKEL")) return Funcao::SHEKEL;
    if (!strcmp(funcName, "HARTMANN")) return Funcao::HARTMANN;
    if (!strcmp(funcName, "SHUBERT")) return Funcao::SHUBERT;
    if (!strcmp(funcName, "BEALE")) return Funcao::BEALE;
    if (!strcmp(funcName, "BOOTH")) return Funcao::BOOTH;
    if (!strcmp(funcName, "BOHACHEVSKY")) return Funcao::BOHACHEVSKY;
    if (!strcmp(funcName, "HUMP")) return Funcao::HUMP;
    if (!strcmp(funcName, "MATYAS")) return Funcao::MATYAS;
    if (!strcmp(funcName, "SCHWEFEL")) return Funcao::SCHWEFEL;
    if (!strcmp(funcName, "COLVILLE")) return Funcao::COLVILLE;
    if (!strcmp(funcName, "PERM")) return Funcao::PERM;
    if (!strcmp(funcName, "PERM0")) return Funcao::PERM0;
    if (!strcmp(funcName, "POWERSUM")) return Funcao::POWERSUM;
    if (!strcmp(funcName, "GRIEWANK")) return Funcao::GRIEWANK;
    if (!strcmp(funcName, "RASTRIGIN")) return Funcao::RASTRIGIN;
    if (!strcmp(funcName, "TRID")) return Funcao::TRID;
    if (!strcmp(funcName, "POWELL")) return Funcao::POWELL;
    if (!strcmp(funcName, "DIXONPRICE")) return Funcao::DIXONPRICE;
    if (!strcmp(funcName, "ACKLEY")) return Funcao::ACKLEY;
    if (!strcmp(funcName, "LEVY")) return Funcao::LEVY;
    if (!strcmp(funcName, "SPHERE")) return Funcao::SPHERE;

    return -1;
}

int getType(char *typeName) {

    if (!strcmp(typeName, "PURO")) return PURO;
    if (!strcmp(typeName, "BFGS")) return BFGS;
    if (!strcmp(typeName, "HIBRIDO")) return HIBRIDO;

    return -1;
}

void saveGaps(double *mediaGaps, int numIter) {
    FILE *arqlog100 = fopen("resultados/gap100.txt", "a+");
    FILE *arqlog500 = fopen("resultados/gap500.txt", "a+");
    FILE *arqlog1000 = fopen("resultados/gap1000.txt", "a+");
    FILE *arqlog5000 = fopen("resultados/gap5000.txt", "a+");
    FILE *arqlog10000 = fopen("resultados/gap10000.txt", "a+");
    FILE *arqlog20000 = fopen("resultados/gap20000.txt", "a+");
    FILE *arqlog50000 = fopen("resultados/gap50000.txt", "a+");

    /*for (int j = 0; j < 7; j++){
            printf("Media do GAP = %lf \n", mediaGaps[j]/(double)numIter);
            fprintf(arqlog, "%lf (%s-%d) - num_aval = %d \n", mediaGaps[j]/(double)numIter, iFuncName, n, j);
    }*/
    printf("Media do GAP 100 = %lf \n", mediaGaps[0] / (double) numIter);
    fprintf(arqlog100, "%lf\n", mediaGaps[0] / (double) numIter);

    printf("Media do GAP 500 = %lf \n", mediaGaps[1] / (double) numIter);
    fprintf(arqlog500, "%lf\n", mediaGaps[1] / (double) numIter);

    printf("Media do GAP 1000 = %lf \n", mediaGaps[2] / (double) numIter);
    fprintf(arqlog1000, "%lf\n", mediaGaps[2] / (double) numIter);

    printf("Media do GAP 5000 = %lf \n", mediaGaps[3] / (double) numIter);
    fprintf(arqlog5000, "%lf\n", mediaGaps[3] / (double) numIter);

    printf("Media do GAP 10000 = %lf \n", mediaGaps[4] / (double) numIter);
    fprintf(arqlog10000, "%lf\n", mediaGaps[4] / (double) numIter);

    printf("Media do GAP 20000 = %lf \n", mediaGaps[5] / (double) numIter);
    fprintf(arqlog20000, "%lf\n", mediaGaps[5] / (double) numIter);

    printf("Media do GAP 50000 = %lf \n", mediaGaps[6] / (double) numIter);
    fprintf(arqlog50000, "%lf\n", mediaGaps[6] / (double) numIter);


    fclose(arqlog100);
    fclose(arqlog500);
    fclose(arqlog1000);
    fclose(arqlog5000);
    fclose(arqlog10000);
    fclose(arqlog20000);
    fclose(arqlog50000);
}

void printSolution(char* type, int n, int size, int fnEvals, double gap, long miliTime) {

    char fileName[30];
    sprintf(fileName, "result_%d_%s_%d.txt", size, type, n);

    FILE *result = fopen(fileName, "a+");

    fprintf(result, "%s %d %lf %ld\n", type, fnEvals, gap, miliTime);
    printf("%s %d %lf %ld\n", type, fnEvals, gap, miliTime);



    fclose(result);
}

int main(int argc, char **argv) {



    char *iFuncName;
    int m, info;
    int maxEvals;
    int iFuncNumb, n, numIter;
    int contSucess = 0;
    int type;
    double *x;
    double epsg, epsf, epsx;
    double maxiters = 0;
    long fnEvals, mediaFnEvals = 0;
    long gradEvals, mediaGradEvals = 0;
    bool sucess;
    int64_t initialTime, finalTime;
    int64_t deltaTime, mediaTime = 0;
    ap::real_1d_array xBFGS;
    MGrasp *mgrasp;
    LBFGS *lbfgs;
    Funcao *func;


    double *gaps, *mediaGaps;

    if (argc < 7) {
        usage();
        return 1;
    }


    int size, rank;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);


    epsg = 0.000001;
    epsf = 0.000001;
    epsx = 0.000001;
    maxiters = 0;

    iFuncName = argv[1];
    iFuncNumb = getFuncNumb(iFuncName);
    n = atoi(argv[2]);

    maxEvals = atoi(argv[3]);
    numIter = atoi(argv[4]);
    type = getType(argv[5]);
    m = atoi(argv[6]);

    if (iFuncNumb == -1) {
        printf("Função %s não existe... \n\n", iFuncName);
        return 1;
    }

    if (type == -1) {
        printf("Tipo %s não existe... \n\n", argv[4]);
        return 1;
    }

    if (m > n) {
        printf("'m' deve ser menor ou igual a 'n' \n\n");
        return 1;
    }

    mediaGaps = new double[7];
    for (int i = 0; i < 7; i++) {
        mediaGaps[i] = 0.0;
    }

    srand(time(NULL));

    long initTime = time(NULL);
    mgrasp = initMGrasp(iFuncNumb, n, &func);
    
    if (size == 1) {
        printf("Gerando sequencial... Max Evals = %d\n", maxEvals);
        mgrasp->start(false, m, maxEvals); 
        long finalTime = time(NULL) - initTime;
        printSolution(iFuncName, n, size, func->getFnEvals(), func->getGap(), finalTime);
    } else {

        if (rank == 0) {
            MPIMaster mpiMaster(size, mgrasp, 20, maxEvals);
            func = mpiMaster.startDaemon();
            long finalTime = time(NULL) - initTime;
            printSolution(iFuncName, n, size, mpiMaster.getResultTotalEvals(), mpiMaster.getResultGAP(), finalTime);

        } else {
            MPISlave mpiSlave(rank, func, lbfgs, maxiters, type, n, m, info, epsg, epsf, epsx, maxEvals, xBFGS, mgrasp);
            mpiSlave.start();
        }

    }
    MPI_Finalize();

    if (false) {
        for (int i = 1; i <= numIter; i++) {
            sucess = false;

            printf("[%d]Iteracao \n", i);
            initialTime = getMilisegundos();

            mgrasp = initMGrasp(iFuncNumb, n, &func);

            if (type == PURO) {
                printf("Puro... \n");
                sucess = mgrasp->start(false, m, maxEvals);
            } else if (type == HIBRIDO) {
                printf("Hibrido... \n");
                sucess = mgrasp->start(true, m, maxEvals);
            } else {
                printf("BFGS... \n");
                x = new double[n];
                mgrasp->unifRandom(x);
                xBFGS.setbounds(1, n);
                for (int j = 0; j < n; j++) {
                    xBFGS(j + 1) = x[j];
                }
                lbfgs = new LBFGS(func, false);
                lbfgs->minimize(n, m, xBFGS, epsg, epsf, epsx, maxiters, info);

                sucess = true;
                printf("Info = %d \n", info);

            }

            finalTime = getMilisegundos();
            printf("\tTime = %ld \n", (long int) (finalTime - initialTime));

            if (maxEvals) {
                gaps = mgrasp->getGaps();
                for (int j = 0; j < 7; j++) {
                    mediaGaps[j] += gaps[j];
                    printf("[%d]Gap[%d] = %lf (%lf)... \n", i, j, mediaGaps[j], gaps[j]);
                }
            } else if (sucess) {
                contSucess++;
                fnEvals = func->getFnEvals();
                gradEvals = func->getGradEvals();
                deltaTime = finalTime - initialTime;

                mediaFnEvals += fnEvals;
                mediaGradEvals += gradEvals;
                mediaTime += deltaTime;
                printf("[%d]Sucesso(%d) = %ld (%ld)... \n", i, contSucess, mediaFnEvals, fnEvals);
                printf("[%d]Grad(%d) = %ld (%ld)... \n", i, contSucess, mediaGradEvals, gradEvals);
            }
            printf("\n");
            delete mgrasp;
            delete func;
        }
    }



    if (false) {
        if (maxEvals) {
            saveGaps(mediaGaps, numIter);
        } else {
            printf("Num execucoes com sucesso... = %d \n", contSucess);
            printf("Media de avaliacao da funcao... = %ld \n", (long) ((double) mediaFnEvals / contSucess));
            printf("Media de avaliacao do gradiente... = %ld \n", (long) ((double) mediaGradEvals / contSucess));
            printf("Media de tempo... = %ld \n", (long) ((double) mediaTime / contSucess));
        }
    }


    printf("Retornando 0...");
    return 0;
}





